package be.castanea.client.model;

import be.castanea.client.delegate.ServerDelegate;
import be.castanea.client.input.CameraHandler;
import be.castanea.client.input.BindingsHandler;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingVolume;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.md5importer.MD5Importer;
import com.md5importer.control.BlendController;
import com.md5importer.control.MD5AnimController;
import com.md5importer.control.MD5NodeController;
import com.md5importer.interfaces.control.IBlendController;
import com.md5importer.interfaces.control.IMD5AnimController;
import com.md5importer.interfaces.control.IMD5NodeController;
import com.md5importer.interfaces.model.IMD5Anim;
import com.md5importer.interfaces.model.IMD5Node;
import com.md5importer.model.MD5Node;
import com.md5importer.test.util.ThreadedUpdater;
import java.io.IOException;
import java.net.URL;

/**
 * Castanea
 * 2009
 * @author Geert van Leemputten, Steven Rymenans, Bart Van Hooydonck
 */
public class Self extends Mob {

    private static Self instance;
    private CameraHandler camera;
    private BindingsHandler input;    
    //private MD5Node model;

    protected IMD5Anim stand;
    protected IMD5Anim walk;

    protected IMD5NodeController controller;
    protected IMD5AnimController walkAnimController;
    protected ThreadedUpdater updater;
    protected IMD5AnimController standAnimController;
    protected IBlendController blender;
    
    public Self(String name) {
        super(name, new Vector3f(0f, 0.8f, 0f));
        //Temporary Rotation fix due to issues between blender and md5importer
        model.getLocalRotation().fromAngles(-1.5f, 0f, 0f);
        //this.attachChild(model);

        camera = new CameraHandler(this);
        input = new BindingsHandler(this);
    }

    public static Self getInstance() {
        return instance;
    }

    public static void loadCharacter(String characterName, Node rootNode) {
        instance = new Self(characterName);        
        scaleAndCenterModel(instance, 2.0f);
        World.getInstance().addMob(instance);        
    }

    @Override
    public void registerWorld(World world) {
        super.registerWorld(world);
        //ServerDelegate.dispatchJoin();
    }

    @Override
    public void setMovement(Movement movement) {
        super.setMovement(movement);
        walk(movement);
        ServerDelegate.dispatchMovementChange();
    }

    @Override
    public void update(float interpolation) {
        input.update(interpolation);
        camera.update(interpolation);
        super.update(interpolation);        
    }

    @Override
    public void loadModel() {
        MD5Importer importer = new MD5Importer();
        IMD5Node md5Model = null;

        // Model url
        // TODO: make params or determine dynamically
        String modelFile = "resources/data/model/hank.md5mesh";
        String standFile = "resources/data/model/hank.md5anim";
        String walkFile = "resources/data/model/hank_walk.md5anim";
       
        try {
            URL modelURL = Self.class.getClassLoader().getResource(modelFile);
            md5Model = importer.loadMesh(modelURL, "self");
            importer.cleanup();            
            URL standURL = Self.class.getClassLoader().getResource(standFile);
            this.stand = importer.loadAnim(standURL, "stand");
            importer.cleanup();
            URL walkURL = Self.class.getClassLoader().getResource(walkFile);
            this.walk = importer.loadAnim(walkURL, "walk");
            importer.cleanup();
            
            // Create controller for body and head.
            this.controller = new MD5NodeController(md5Model);
            // Set active animations.
            this.controller.setActiveAnim(stand);
            // Create animation controllers.
            this.walkAnimController = new MD5AnimController(walk);
            this.walkAnimController.setActive(false);
            // Create stand controller.
            this.standAnimController = new MD5AnimController(stand);
            this.standAnimController.setActive(true);
            // Create a blender.
            this.blender = new BlendController(md5Model, this.controller);
            // Create a threaded updater to update animations in separate thread.
            this.updater = new ThreadedUpdater(60);
            this.updater.addController(this.walkAnimController);
            this.updater.addController(this.standAnimController);
            this.updater.addController(this.blender);
            this.updater.start();
            this.model = (MD5Node)md5Model;
        } catch (IOException e) {
            e.printStackTrace();
        }       
    }

    private void walk(Movement movement) {        
        if(this.controller.getActiveAnim().getName().equals("walk")
                & (movement.getForwardSpeed() == 0 | movement.getTurnSpeed() == 0)) {
            this.walkAnimController.setActive(false);
            this.blender.blend(this.stand, this.standAnimController, 0.5f);
        } else if(this.controller.getActiveAnim().getName().equals("stand")
                & (movement.getForwardSpeed() > 0 | movement.getTurnSpeed() != 0)) {
            this.standAnimController.setActive(false);
            this.blender.blend(this.walk, this.walkAnimController, 0.5f);
        }
    }

    protected static void scaleAndCenterModel(Node model, float size) {
        model.updateGeometricState(0, true);

        BoundingVolume worldBound = model.getWorldBound();
        if (worldBound == null) {
            model.setModelBound(new BoundingBox());
            model.updateModelBound();
            model.updateGeometricState(0, true);
            worldBound = model.getWorldBound();
        }

        if (worldBound != null){
            Vector3f center = worldBound.getCenter();

            BoundingBox boundingBox = new BoundingBox(center, 0, 0, 0);
            boundingBox.mergeLocal(worldBound);

            Vector3f extent = boundingBox.getExtent( null );
            float maxExtent = Math.max( Math.max( extent.x, extent.y ), extent.z );
            float height = extent.y;
            if ( maxExtent != 0 ) {
                model.setLocalScale( size / maxExtent );
                Vector3f pos = center.negate().addLocal(0.0f, height / 2.0f, 0.0f);
                model.setLocalTranslation(pos);
                model.updateGeometricState(0, true);
                model.updateModelBound();
            }
        }
    }
}
